/*
 * Pitch.java
 *
 * Copyright (c) 2010 Jaymie Strecker (strecker at cs.umd.edu). All rights reserved.
 *
 * This file is part of Tonic.
 *
 * Tonic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tonic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tonic.  If not, see <http://www.gnu.org/licenses/>.
 */

package tonic.audio;

/**
 *
 * @author Jaymie Strecker (strecker at cs.umd.edu)
 */
public class Pitch {
    private int octave;    // 0 <= octave --- each octave starts with C (not A)
    private int semitone;  // 0 <= semitone <= 11 --- this is the offset from C
    private static final int MAX_SEMITONE = 11;

    protected Pitch(int octave, int semitone) throws IllegalArgumentException {
        if (octave < 0) {
            throw new IllegalArgumentException("Octave is negative.");
        }
        if (semitone < 0 || semitone > 11) {
            throw new IllegalArgumentException("Semitone not in range [0..11]."); 
        }
        this.octave = octave;
        this.semitone = semitone;
    }

    protected int getOctaveNum() {
        return octave;
    }

    protected int getSemitoneNum() {
        return semitone; 
    }
    
    protected float frequency(int quarterStepOffset) {
        // Formula from http://en.wikipedia.org/wiki/Note
        double offsetFromA4 = 12*(octave - 4) + semitone - 9 + 0.5*quarterStepOffset;
        return (float) Math.pow(2, offsetFromA4/12) * 440;
    }

    protected int number() {
        return 12 * octave + semitone; 
    }

    /**
     * Returns a string representation of the pitch.
     *
     * @param sharps    true to use sharps for accidentals, false to use flats
     */
    public String getSemitone(boolean sharps) {
        String s;
        switch (semitone) {
            case 0: s = "C"; break;
            case 1: s = sharps ? "C#" : "Db"; break;
            case 2: s = "D"; break;
            case 3: s = sharps ? "D#" : "Eb"; break;
            case 4: s = "E"; break;
            case 5: s = "F"; break;
            case 6: s = sharps ? "F#" : "Gb"; break;
            case 7: s = "G"; break;
            case 8: s = sharps ? "G#" : "Ab"; break;
            case 9: s = "A"; break;
            case 10: s = sharps ? "A#" : "Bb"; break;
            case 11: s = "B"; break;
            default: s = "ERROR"; 
        }
        return s;
    }

    public static int getMaxSemitone() {
        return MAX_SEMITONE; 
    }

    @Override
    public boolean equals(Object o) {
        if (! (o instanceof Pitch)) {
            return false;
        } else {
            Pitch other = (Pitch) o;
            return other != null && octave == other.octave && semitone == other.semitone;
        }
    }

    @Override
    public int hashCode() {
        return number(); 
    }

    @Override
    public String toString() {
        return getSemitone(true) + Integer.toString(octave);
    }
}
